#include <string.h>
#include "gc.h"
#include "mem.h"
#include "eval.h"
#include "redmapobject.h"
#include "redcodeobject.h"
#include "redarrayobject.h"
#include "redframeobject.h"
#include "redtupleobject.h"
#include "redfunctionobject.h"
#include "redexceptionobject.h"

static void function_clear(RedObject *self)
{
	Red_UNREF(RedFunction_Code(self));
	Red_UNREF(RedFunction_Locals(self));
	Red_UNREF(RedFunction_Closure(self));
}

static void function_release(RedObject *self)
{
	function_clear(self);
	Red_RELEASE();
}

static void function_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(RedFunction_Code(self));
	Red_VISIT(RedFunction_Locals(self));
	Red_VISIT(RedFunction_Closure(self));
}

RedTypeObject RedType_Function =
{
	RedType_INIT("func", RedFunctionObject, RedType_Object)

	.tp_clear		= function_clear,
	.tp_release		= function_release,
	.tp_traverse	= function_traverse,
};

RedObject *RedFunction_Invoke(RedObject *self, RedObject *args)
{
	if (!RedTuple_Exact(args))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'args' argument");
		return NULL;
	}

	if (!RedFunction_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'function' expected for 'self' argument");
		return NULL;
	}

	/* get arguments info from code */
	RedObject *code = RedFunction_Code(self);

	/* variadic function */
	if (RedCode_VarArg(code) && (RedTuple_Length(args) < RedFunction_Args(self)))
	{
		RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes at least %d arguments, but %d given", RedFunction_Args(self), RedTuple_Length(args));
		return NULL;
	}

	/* fixed-argument function */
	if (!RedCode_VarArg(code) && (RedTuple_Length(args) != RedFunction_Args(self)))
	{
		RedException_ThrowFormat(&RedExc_ArgumentError, "Function takes exactly %d arguments, but %d given", RedFunction_Args(self), RedTuple_Length(args));
		return NULL;
	}

	/* create a new function frame */
	RedObject *locals = RedMap_Clone(RedFunction_Locals(self));
	RedObject *context = RedFrame_Acquire(code, locals, RedFunction_Closure(self));

	/* cannot acquire function frame, eval is impossible */
	if (!context)
		return NULL;

	int i = 0;
	RedObject *names = RedCode_Args(code);

	/* load arguments into frame locals */
	for (; i < RedFunction_Args(self); i++)
		RedMap_PutString(locals, RedArray_Items(names)[i], RedTuple_Items(args)[i]);

	/* if any var-arg argument, load into locals */
	if (RedCode_VarArg(code))
	{
		RedObject *name = RedCode_VarArg(code);
		RedObject *tuple = RedTuple_FromLength(RedTuple_Length(args) - i);

		for (int j = 0; i < RedTuple_Length(args); i++, j++)
		{
			Red_REF(RedTuple_Items(args)[i]);
			RedTuple_Items(tuple)[j] = RedTuple_Items(args)[i];
		}

		RedMap_PutString(locals, name, tuple);
		Red_UNREF(tuple);
	}

	/* eval with the new frame */
	RedObject *result = RedEval_ExecFrame(context);

	/* release frame */
	Red_UNREF(locals);
	Red_UNREF(context);
	return result;
}

RedObject *RedFunction_WrapCode(RedObject *code, RedObject *closure, int args)
{
	if (!RedCode_Exact(code))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'map' expected for 'self' argument");
		return NULL;
	}

	RedObject *result = RedObject_Create(&RedType_Function, NULL);

	if (!result)
		return NULL;

	Red_REF(code);
	Red_REF(closure);
	RedFunction_Args(result) = args;
	RedFunction_Code(result) = code;
	RedFunction_Closure(result) = closure;

	/* free variables buffer */
	if (!(RedFunction_Locals(result) = RedMap_CreateStringKey()))
	{
		Red_UNREF(result);
		return NULL;
	}

	/* add to GC track list */
	RedObject_GC_Insert(result);
	RedObject_GC_Insert(RedFunction_Locals(result));

	/* free variables that already defined must be eval'ed now */
	for (int i = 0; i < RedArray_Length(RedCode_Frees(code)); i++)
	{
		RedObject *var = NULL;
		RedObject *name = RedArray_Items(RedCode_Frees(code))[i];
		RedFrameObject *frame = (RedFrameObject *)closure;

		/* resolve recursively in this scope and parent scopes */
		while (!var && frame)
		{
			var = RedMap_GetString(RedFrame_Locals(frame), name);
			frame = (RedFrameObject *)(frame->closure);
		}

		/* variables doesn't exist in parent scope now may exist the global scope,
		 or parent will create it later, the interpreter will handle it */
		if (var)
			RedMap_PutString(RedFunction_Locals(result), name, var);
	}

	return result;
}
